/**
 * 
 */
package kr.co.insoft.driver.openstack;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import kr.co.insoft.driver.Drivers;
import kr.co.insoft.driver.exception.DriverException;
import kr.co.insoft.driver.exception.ExceptionCode;
import kr.co.insoft.driver.exception.NotSupportedException;
import kr.co.insoft.driver.model.HardwareProfileModel;
import kr.co.insoft.driver.model.ImageModel;
import kr.co.insoft.driver.model.InstanceModel;
import kr.co.insoft.driver.model.InstanceResultModel;
import kr.co.insoft.driver.model.KeyPairModel;
import kr.co.insoft.driver.model.OSTypeModel;
import kr.co.insoft.driver.model.RegionModel;
import kr.co.insoft.driver.model.SecurityModel;
import kr.co.insoft.driver.model.VolumeModel;
import kr.co.insoft.driver.model.ZoneModel;
import kr.co.insoft.driver.openstack.model.OpenStackCredentialModel;
import kr.co.insoft.driver.openstack.model.OpenStackInstanceModel;
import kr.co.insoft.driver.openstack.model.OpenStackInstanceResultModel;
import kr.co.insoft.driver.openstack.process.CreateServerProcess;
import kr.co.insoft.driver.openstack.process.DeleteServerProcess;
import kr.co.insoft.driver.openstack.process.DetailServerProcess;
import kr.co.insoft.driver.openstack.process.HardwareProcess;
import kr.co.insoft.driver.openstack.process.ImageProcess;
import kr.co.insoft.driver.openstack.process.ZoneProcess;

import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.Assert;

/**
 * @author IN-SOFT Inc. skpark@in-soft.co.kr
 * 
 */
public class OpenStackDriver implements Drivers {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3840345247891633851L;

	private static final Logger logger = LoggerFactory
			.getLogger(OpenStackDriver.class);

	protected OpenStackCredentialModel credentialModel;
	protected ComputeServiceContext context = null;
	protected ComputeService computeService = null;

	private void vaildate() throws DriverException {
		try {
			Assert.notNull(credentialModel);
			Assert.notNull(credentialModel.getEndpoint());
			Assert.notNull(credentialModel.getUsername());
			Assert.notNull(credentialModel.getAccessKey());
		} catch (IllegalArgumentException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("credentialModel : {}", credentialModel);
			}
			throw new DriverException(ExceptionCode.ARGUMENT_ERROR, e);
		}
	}

	public OpenStackDriver() {
		this.credentialModel = new OpenStackCredentialModel();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setEndpoint(java.lang.String)
	 */
	@Override
	public void setEndpoint(String endpoint) {
		this.credentialModel.setEndpoint(endpoint);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setAccessKey(java.lang.String)
	 */
	@Override
	public void setAccessKey(String accessKey) {
		this.credentialModel.setAccessKey(accessKey);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setApiKey(java.lang.String)
	 */
	@Override
	public void setApiKey(String apiKey) {
		this.credentialModel.setAccessKey(apiKey);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setSecretKey(java.lang.String)
	 */
	@Override
	public void setSecretKey(String secretKey) {
		this.credentialModel.setSecretKey(secretKey);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setUsername(java.lang.String)
	 */
	@Override
	public void setUsername(String username) {
		this.credentialModel.setUsername(username);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#setProvider(java.lang.String)
	 */
	@Override
	public void setProvider(String provider) {
		this.credentialModel.setProvider(provider);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getConnection(java.lang.String)
	 */
	@Override
	public Object getConnection(String endpoint) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getConnection()
	 */
	@Override
	public Object getConnection() throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getRegions()
	 */
	@Cacheable("regions")
	public Collection<? extends RegionModel> getRegions()
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getZones()
	 */
	@Override
	public Collection<? extends ZoneModel> getZones() throws DriverException,
			NotSupportedException {
		vaildate();
		try {
			ZoneProcess zoneProcess = new ZoneProcess(credentialModel);
			try {
				return zoneProcess.getZonesConvertRackSpace();
			} finally {
				zoneProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getImages()
	 */
	@Override
	public Collection<? extends ImageModel> getImages() throws DriverException,
			NotSupportedException {
		vaildate();
		try {
			ImageProcess imageProcess = new ImageProcess(credentialModel);
			try {
				return imageProcess.getImagesConvertOpenStack();
			} finally {
				imageProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getMyImages()
	 */
	@Override
	public Collection<? extends ImageModel> getMyImages()
			throws DriverException, NotSupportedException {
		vaildate();
		try {
			ImageProcess imageProcess = new ImageProcess(credentialModel);
			try {
				return imageProcess.getMyImagesConvertOpenStack();
			} finally {
				imageProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}
	
	/* (non-Javadoc)
	 * @see kr.co.insoft.driver.Drivers#getMyImages(java.lang.String)
	 */
	@Override
	public Collection<? extends ImageModel> getMyImages(String zone)
			throws DriverException, NotSupportedException {
		vaildate();
		try {
			ImageProcess imageProcess = new ImageProcess(credentialModel);
			try {
				return imageProcess.getMyImagesConvertOpenStack(zone);
			} finally {
				imageProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getSecures()
	 */
	@Override
	public Collection<? extends SecurityModel> getSecures()
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getKeyPairs()
	 */
	@Override
	public Collection<? extends KeyPairModel> getKeyPairs()
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * kr.co.insoft.driver.Drivers#createInstance(kr.co.insoft.driver.model.
	 * InstanceModel)
	 */
	@Override
	public InstanceResultModel createInstance(InstanceModel instance)
			throws DriverException, NotSupportedException {
		try {
			Assert.notNull(instance);
			Assert.notNull(instance.getInstanceName());
			Assert.notNull(instance.getInstanceType());
			Assert.notNull(instance.getImageId());
		} catch (IllegalArgumentException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("{}", instance);
			}
			throw new DriverException(ExceptionCode.ARGUMENT_ERROR, e);
		}
		try {
			HardwareProcess hardwareProcess = new HardwareProcess(
					credentialModel);
			ImageProcess imageProcess = new ImageProcess(credentialModel);
			CreateServerProcess createServerProcess = new CreateServerProcess(
					credentialModel);
			try {
				Hardware hardware = hardwareProcess.getHardware(
						instance.getZone(), instance.getInstanceType());
				if (logger.isDebugEnabled())
					logger.debug("using hardware : {}", hardware);
				Image image = imageProcess.getImage(instance.getImageId());
				if (logger.isDebugEnabled())
					logger.debug("using image : {}", image);
				OpenStackInstanceModel resultModel = createServerProcess
						.createServer(instance.getInstanceName().toLowerCase(),
								instance.getZone(), hardware, image);
				OpenStackInstanceResultModel result = new OpenStackInstanceResultModel();
				List<OpenStackInstanceModel> instances = new ArrayList<OpenStackInstanceModel>();
				instances.add(resultModel);
				result.setInstances(instances);
				return result;
			} finally {
				hardwareProcess.close();
				imageProcess.close();
				createServerProcess.close();
			}
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using Instance Model : {}", instance);
			}
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * kr.co.insoft.driver.Drivers#createInstanceWithImage(kr.co.insoft.driver
	 * .model.InstanceModel)
	 */
	@Override
	public InstanceResultModel createInstanceWithImage(InstanceModel instance)
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * kr.co.insoft.driver.Drivers#renameInstance(kr.co.insoft.driver.model.
	 * InstanceModel)
	 */
	@Override
	public void renameInstance(InstanceModel instance) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getInstances(java.lang.String[])
	 */
	@Override
	public Collection<? extends InstanceModel> getInstances(
			String... instanceIds) throws DriverException,
			NotSupportedException {
		try {
			DetailServerProcess detailServerProcess = new DetailServerProcess(
					credentialModel);
			try {
				return detailServerProcess.getServers(instanceIds);
			} finally {
				detailServerProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getInstances(java.util.Collection)
	 */
	@Override
	public Collection<? extends InstanceModel> getInstances(
			Collection<String> instanceIds) throws DriverException,
			NotSupportedException {
		return getInstances(instanceIds.toArray(new String[] {}));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getInstances()
	 */
	@Override
	public Collection<? extends InstanceModel> getInstances()
			throws DriverException, NotSupportedException {
		try {
			DetailServerProcess detailServerProcess = new DetailServerProcess(
					credentialModel);
			try {
				return detailServerProcess.getServers();
			} finally {
				detailServerProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#startInstances(java.lang.String[])
	 */
	@Override
	public Collection<? extends InstanceModel> startInstances(
			String... instanceIds) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#startInstances(java.util.Collection)
	 */
	@Override
	public Collection<? extends InstanceModel> startInstances(
			Collection<String> instanceIds) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#stopInstances(java.lang.String[])
	 */
	@Override
	public Collection<? extends InstanceModel> stopInstances(
			String... instanceIds) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#stopInstances(java.util.Collection)
	 */
	@Override
	public Collection<? extends InstanceModel> stopInstances(
			Collection<String> instanceIds) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * kr.co.insoft.driver.Drivers#terminateInstances(kr.co.insoft.driver.model
	 * .InstanceModel)
	 */
	@Override
	public Collection<? extends InstanceModel> terminateInstances(
			InstanceModel instance) throws DriverException,
			NotSupportedException {
		vaildate();
		try {
			Assert.notNull(instance);
			Assert.notNull(instance.getInstanceId());
		} catch (IllegalArgumentException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("using instance : {}", instance);
			}
			throw new DriverException(ExceptionCode.ARGUMENT_ERROR, e);
		}

		try {
			DeleteServerProcess deleteServerProcess = new DeleteServerProcess(
					credentialModel);
			try {
				return deleteServerProcess.terminateServer(instance
						.getInstanceId());
			} finally {
				deleteServerProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#terminateInstances(java.util.Collection)
	 */
	@Override
	public Collection<? extends InstanceModel> terminateInstances(
			Collection<InstanceModel> instances) throws DriverException,
			NotSupportedException {
		vaildate();
		try {
			Assert.notNull(instances);
			for (InstanceModel instance : instances)
				Assert.notNull(instance.getInstanceName());
		} catch (IllegalArgumentException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("using instance : {}", instances);
			}
			throw new DriverException(ExceptionCode.ARGUMENT_ERROR, e);
		}

		try {
			DeleteServerProcess deleteServerProcess = new DeleteServerProcess(
					credentialModel);
			try {
				Set<String> instanceIds = new HashSet<String>();
				for (InstanceModel instance : instances) {
					instanceIds.add(instance.getInstanceId());
				}
				return deleteServerProcess.terminateServer(instanceIds);
			} finally {
				deleteServerProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getVolumes()
	 */
	@Override
	public Collection<? extends VolumeModel> getVolumes()
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getVolumes(java.util.Collection)
	 */
	@Override
	public Collection<? extends VolumeModel> getVolumes(
			Collection<String> volumeIds) throws DriverException,
			NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#createVolumes()
	 */
	@Override
	public void createVolumes() throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#attachVolume()
	 */
	@Override
	public void attachVolume() throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getOsTypes()
	 */
	@Override
	public Collection<? extends OSTypeModel> getOsTypes()
			throws DriverException, NotSupportedException {
		throw new NotSupportedException(ExceptionCode.NOT_SUPPORTED);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getHardwareProfiles()
	 */
	@Cacheable("hardwares")
	public Collection<? extends HardwareProfileModel> getHardwareProfiles()
			throws DriverException, NotSupportedException {
		vaildate();
		try {
			HardwareProcess hardwareProcess = new HardwareProcess(
					credentialModel);
			try {
				return hardwareProcess.getHardwaresConvertOpenStack();
			} finally {
				hardwareProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getHardwareProfiles(java.lang.String)
	 */
	@Cacheable(value = "hardwareByZones", key = "#zone")
	public Collection<? extends HardwareProfileModel> getHardwareProfiles(
			String zone) throws DriverException, NotSupportedException {
		vaildate();
		try {
			HardwareProcess hardwareProcess = new HardwareProcess(
					credentialModel);
			try {
				return hardwareProcess.getHardwaresConvertOpenStack(zone);
			} finally {
				hardwareProcess.close();
			}
		} catch (Exception e) {
			throw new DriverException(ExceptionCode.DRIVER_ERROR, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getCurrentState()
	 */
	@Override
	@Async
	public Collection<? extends InstanceModel> getCurrentState()
			throws DriverException, NotSupportedException {
		return getInstances();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see kr.co.insoft.driver.Drivers#getCurrentState(java.util.Collection)
	 */
	@Override
	@Async
	public Collection<? extends InstanceModel> getCurrentState(
			Collection<? extends InstanceModel> instances)
			throws DriverException, NotSupportedException {
		Set<String> instanceIds = new HashSet<String>();
		for (InstanceModel model : instances)
			instanceIds.add(model.getInstanceId());
		return getInstances(instanceIds);
	}

}
